package com.test.config;

import com.test.aop.LogAspect;
import com.test.aop.MathCalculator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

/**
 * @author lzy
 * @version v1.0
 * Create DateTime: 2018/11/29 16:08
 * <p>
 * AOP:【动态代理】指在程序运行期间将某段代码切入到指定方法指定位置进行运行的编程方式
 * 1.导入aop模块：spring-aop
 * 2.定义一个业务逻辑类，在业务运行的时候将日志进行打印（方法之前，方法运行结束，方法出现异常）
 * 3.定义一个日志切面类，切面类里面的方法需要动态感知业务逻辑类运行到哪一步了
 * 通知方法：
 * ①前置通知（@Before）：logStart()在目标方法div运行之前运行
 * ②后置通知（@Return）：logEnd()在目标方法div运行之后运行，无论方法正常结束还是异常结束
 * ③返回通知(@AfterReturning)：logReturn()在目标方法正常返回之后运行
 * ④异常通知(@AfterThrowing)：logException()在目标方法运行出现异常后运行
 * ⑤环绕通知(@Around)：动态代理，手动推进目标方法运行（joinPoint.process()）
 * <p>
 * 4.给切面类目标方法标注何时何地运行（通知注解）
 * 5.将切面类和业务逻辑类（目标方法所在的类）都加入到容器中
 * 6.告诉Spring哪个类是切面类（给切面类上加上一个注解@Aspect）
 * 7.给配置类加@EnableAspectJAutoProxy【开启基于注解的aop模式】
 * 在Spring中有很多的@Enablexxx功能
 * <p>
 * 主要把握三步：
 * 1.将业务逻辑组件和切面类都加入到容器中，告诉Spring哪个是切面类（@Aspect）
 * 2.在切面类上的每个通知方法上标注通知注解，告诉spring何时何地运行（切入点表达式）
 * 3.开启基于注解的aop模式 @EnableAspectJAutoProxy
 * <p>
 * AOP原理：@EnableAspectJAutoProxy
 * 1.@EnableAspectJAutoProxy是什么？
 * @Import(AspectJAutoProxyRegistrar.class)，给容器导入AspectJAutoProxyRegistrar组件 利用AspectJAutoProxyRegistrar自定义给容器中注册bean：
 * <p>
 * 流程：
 * 1.传入配置类，，创建ioc容器
 * 2.注册配置类，调用refresh（）方法刷新容器
 * 3.registerBeanPostProcessors(beanFactory);注册bean后置处理器来方便拦截bean的创建
 * 3.1 先获取ioc容器中已经定义了的需要创建对象的所有BeanPostProcessor
 * 3.2 给容器中加入其它的BeanPostProcessor
 * 3.3 优先注册实现了PriorityOrdered接口的BeanPostProcessor
 * 3.4 再给容器中注册实现了Ordered接口的BeanPostProcessor
 * 3.5 注册没实现优先级接口的BeanPostProcessor
 * 3.6 祖册BeanPostProcessor，实际上就是创建BeanPostProcessor对象，保存在容器中
 * 创建internalAutoProxyCreator的BeanPostProcessor【AnnotationAwareAspectJAutoProxyCreator】
 *   1.创建Bean的实例
 *   2.populateBean:给bean的各种属性赋值
 *   3.initializeBean:初始化bean
 *     ①invokeAwareMethods()：处理Aware接口的方法回调
 *     ②applyBeanPostProcessorsBeforeInitialization():应用后置处理器的PostProcessorsBeforeInitialization方法
 *     ③invokeInitMethods：执行自定义的初始化方法
 *     ④appllyBeanPostProcessorAfterInitialization，应用后置处理器的PostProcessorAfterInitialization方法
 *   4.BeanPostProcessor（AnnotationAwareAspectJAutoProxyCreator）创建成功 --> aspectJAdvisorsBuilder
 * 3.7 把BeanPostProcessor注册到BeanFactory中：
 *     beanFactory.addBeanPostProcessor(postProcessor)
 *
 * ----------------以上是创建和注册AnnotationAwareAspectJAutoProxyCreator的过程---------
 * AnnotationAwareAspectJAutoProxyCreator -> InstantiationAwareBeanPostProcessor
 * 4.finishBeanFactoryInitialization(beanFactory);完成beanFactory初始化工作，创建剩下的单实例bean
 *   4.1 遍历获取容器中所有的Bean，依次创建对象getBean
 *      getBean() -> doGetBean() -> getSingleton()
 *   4.2 创建bean
 *       【AnnotationAwareAspectJAutoProxyCreator在所有bean创建之前会有一个拦截，InstantiationAwareBeanPostProcessor会调用PostProcessorBeforeInitialization方法】
 *      ①先从缓存中获取当前bean，如果能获取到，说明bean是之前被创建过的；直接使用，否则再创建。只要创建好的bean，都会被缓存起来
 *      ②createBean():创建bean：AnnotationAwareAspectJAutoProxyCreator会在任何bean创建之前尝试返回bean的实例
 *      【BeanPostProcessor是在Bean对象创建完成初始化前后调用的】
 *      【InstantiationAwareBeanPostProcessor是在创建Bean实例之前先尝试用后置处理器返回对象】
 *        2.1 resolveBeforeInstantiation(String beanName, RootBeanDefinition mbd)，解析BeforeInstantiation，希望后置处理器在此能返回一个代理对象，如果能返回代理对象就使用，如果不能就继续
-后置处理器先尝试返回对象，拿到所有后置处理器，如果是InstantiationAwareBeanPostProcessor类型的，就执行PostProcessorBeforeInitialization方法
 *        2.2 doCreateBean(beanName, mbdToUse, args)真正的去创建一个bean实例，和3.6流程一样
 *
 *   AnnotationAwareAspectJAutoProxyCreator【InstantiationAwareBeanPostProcessor】的作用：
 *   1.在每一个bean初始化之前调用PostProcessorBeforeInitialization方法
 *     关心MathCalculator和LogAspect的创建
 *     1）判断当前bean是否在advisedBeans中，保存了需要增强的bean
 *     2）判断当前bean是否是基础类型的Advice、Pointcut、Advisor，AopInfrastructureBean或者是否是切面（@Aspect）
 *     3）是否需要跳过
 *      3.1 获取候选的增强器（切面里的通知方法）【List<Advisor> candidateAdvisors】
 *          每一个封装的通知方法的增强器是InstantiationModelAwarePointcutAdvisor类型的
 *          判断每一个增强器是否是AspectJPointcutAdvisor类型的，返回true
 *      3.2 永远返回false
 *   2.创建对象后调用PostProcessorAfterInitialization方法
 *     return wrapIfNecessary(bean，beanName, cacheKey);//包装，如果需要的情况下
 *       2.1 获取当前bean的所有的增强器（通知方法）：Object[] specificInterceptors
 *       ①找到候选的增强器（哪些通知方法是需要切入当前方法的）
 *       ②获取到能在bean使用的增强器
 *       ③给增强器排序
 *       2.2 保存当前bean在advisedBeans中
 *       2.3 如果当前bean需要增强，创建当前bean的代理对象
 *         ①获取所有增强器（通知方法）
 *         ②保存到proxyFactory
 *         ③创建代理对象：Spring自动决定
 *           -JdkDynamicAopProxy(config) jdk动态代理
 *           -ObjenesisCglibAopProxy(config)  cglib动态代理
 *       2.4 给容器中返回当前组件使用cglib增强了的代理对象
 *       2.5.以后容器中获取到的就是这个组件的代理对象，执行目标方法的时候，代理对象就会执行通知方法的流程
 *
 * 3.目标方法的执行
 *    容器中保存了组件的代理对象（cglib增强后的对象），这个对象里面保存了详细信息（比如增强器，目标对象，xxx）
 *    1）CglibAopProxy.intercept()：拦截目标方法的执行
 *    2）根据ProxyFactory获取目标方法拦截器链:
 *        List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(methods[x], rootClass);
 *        ①List<Object> interceptorList保存所有拦截器，1个默认ExposeInvocationInterceptor和4个增强器
 *        ②遍历所有的增强器，将其转为interceptor[]
 *          registry.getInterceptors(advisor)
 *        ③将增强器转换为List<MethodInterceptor>,如果是MethodInterceptor，直接加入集合中，如果不是，使用AdvisorAdaptor转换为MethodInterceptor
 *        转换完成返回MethodInterceptor数组
 *
 *    3）如果没有拦截器链，直接执行目标方法
 *    拦截器链：每一个通知方法又被包装为拦截器，利用MethodInterceptor机制
 *
 *    4）如果有拦截器链，把需要执行的目标对象，目标方法，拦截器链等信息传入创建一个CglibMethodInvocation对象
 *    并调用 Object returnValue = proceed（）方法
 *    5）拦截器链触发过程
 *    ①如果没有拦截器，直接执行目标方法，或者拦截器的索引和拦截器数组-1大小一样（执行到了最后一个拦截器）
 *    每次执行proceed，索引都会自增一次
 *    ②链式获取每一个拦截器，拦截器执行invoke方法，每一个拦截器等待下一个拦截器执行完成返回以后再来执行，拦截器链的机制，保证通知方法和目标方法的执行顺序
 *
 *
 * 总结：
 *   1）利用@EnableAspectJAutoProxy开启AOP功能
 *   2）@EnableAspectJAutoProxy会给容器中注册一个组件AnnotationAwareAspectJAutoProxyCreator
 *   3）AnnotationAwareAspectJAutoProxyCreator是一个后置处理器
 *   4）容器创建的流程
 *   ①registerBeanPostProcessors()注册后置处理器，创建AnnotationAwareAspectJAutoProxyCreator对象
 *   ②finishBeanFactoryInitialization()初始化剩下的单实例bean
 *     -创建业务逻辑组件和切面组件
 *     -AnnotationAwareAspectJAutoProxyCreator拦截组件的创建过程
 *     -组件创建完成后，判断组件是否需要增强，如果是，切面的通知方法包装成增强器（Advisor），给业务逻辑组件创建一个代理对象
 *    5）执行目标方法
 *      5.1 代理对象执行目标方法
 *      5.2 CglibAopProxy.intercept()
 *        - 得到目标方法的拦截器链（增强器包装成拦截器MethodInterceptor）
 *        - 利用拦截器的链式机制，依次进入每一个拦截器进行执行
 *        - 效果：
 *           * 正常执行： 前置通知 -> 目标方法 -> 后置通知 -> 返回通知
 *           * 正常执行： 前置通知 -> 目标方法 -> 后置通知 -> 异常通知
 *
 */
@EnableAspectJAutoProxy
@Configuration
public class MainConfigAop {

    //业务逻辑类加入到容器中
    @Bean
    public MathCalculator mathCalculator() {
        return new MathCalculator();
    }

    //切面类加入容器中
    @Bean
    public LogAspect logAspect() {
        return new LogAspect();
    }


}
